package com.keguoyu.easymvp.compiler;

import java.lang.ref.Reference;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedOptions;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;

import com.google.auto.service.AutoService;
import com.keguoyu.easymvp.annotation.Nullable;
import com.keguoyu.easymvp.annotation.Provider;
import com.keguoyu.easymvp.compiler.provide.ProviderBuilder;
import com.keguoyu.easymvp.compiler.base.BaseProcessor;
import com.keguoyu.easymvp.compiler.utils.ElementUtils;
import com.squareup.javapoet.TypeName;

@AutoService(Processor.class)
@SupportedOptions({ProviderProcessor.GENERATE_PKG_NAME})
public class ProviderProcessor extends BaseProcessor {
  static final String GENERATE_PKG_NAME = "generatePkg";

  private boolean mHasProcessed;

  @Override
  public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    if (mHasProcessed) {
      return false;
    }
    parseAccess(roundEnvironment);
    mHasProcessed = true;
    return false;
  }

  private void parseAccess(RoundEnvironment roundEnvironment) {
    ElementUtils entries = ElementUtils.fromRoundEnv(roundEnvironment, Provider.class);
    for (Map.Entry<TypeElement, List<Element>> entry : entries) {
      List<Element> fields = entry.getValue();
      if (fields == null) {
        continue;
      }
      generateClass(entry.getKey(), fields);
    }
  }


  private void generateClass(Element root, List<Element> fields) {
    if (root == null || root.getKind() != ElementKind.CLASS) {
      return;
    }
    String pkg = mElementUtils.getPackageOf(root).toString();
    ProviderBuilder providerBuilder =
        new ProviderBuilder(pkg, root.getSimpleName().toString(), mAutoGenerated, TypeName.get(root.asType()));
    for (Element field : fields) {
      Provider provider = field.getAnnotation(Provider.class);
      if (provider == null) {
        continue;
      }
      String key = provider.value();
      String fieldName = field.getSimpleName().toString();
      boolean canBeNull = field.getAnnotation(Nullable.class) != null;
      TypeMirror fieldType = mTypeUtils.erasure(field.asType());
      if (field.getKind() == ElementKind.FIELD) {
        if ("".equals(key)) {
          providerBuilder.onFieldByType(key, fieldName,
              TypeName.get(fieldType), canBeNull, mTypeUtils.isAssignable(fieldType,
                  mTypeUtils
                      .erasure(mElementUtils.getTypeElement(Reference.class.getName()).asType())));
        } else {
          providerBuilder.onFieldByName(key, fieldName,
              TypeName.get(fieldType), canBeNull, mTypeUtils.isAssignable(fieldType,
                  mTypeUtils
                      .erasure(mElementUtils.getTypeElement(Reference.class.getName()).asType())));
        }
      } else if (field.getKind() == ElementKind.METHOD) {
        ExecutableElement executableElement = (ExecutableElement) field;
        Name simpleName = executableElement.getSimpleName();
        TypeMirror returnType = executableElement.getReturnType();
        providerBuilder.onMethod(key, simpleName.toString(), TypeName.get(returnType));
      }
    }
    writeClass(providerBuilder.getPkg(), providerBuilder.getCls(), providerBuilder.build());
  }


  @Override
  public Set<String> getSupportedAnnotationTypes() {
    Set<String> set = new HashSet<>();
    set.add(Provider.class.getName());
    return set;
  }

  @Override
  public SourceVersion getSupportedSourceVersion() {
    return SourceVersion.RELEASE_8;
  }

}
